package fctuc.dei.myTwitter.client;

import static fctuc.dei.myTwitter.constants.MyTwitterConstants.MAX_ATTEMPTS;
import static fctuc.dei.myTwitter.constants.MyTwitterConstants.TCP_LISTENER_PORT_ALTERNATIVE;
import static fctuc.dei.myTwitter.constants.MyTwitterConstants.WATCHDOG;
import static fctuc.dei.myTwitter.constants.MyTwitterConstants.sleep;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.nio.channels.SelectionKey;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import fctuc.dei.myTwitter.com.Message;


public class SocketConnection extends ServerConnection {

	private Socket socket;
	private ObjectInputStream in;
	private ObjectOutputStream out;
	private Object mutex = new Object(); //bloqueia a thread enquanto espera
	private BlockingQueue<Message> queue;


	public SocketConnection(MyTwitterClient c){
		super(c);
		queue = new LinkedBlockingQueue<Message>();
		new NetworkIn().start();
		new NetworkOut(this.queue).start();
	}

	@Override
	public void realConnect() {
		
		for(int i = 0; i < MAX_ATTEMPTS; i++){
			boolean sucess = false;
			try{
				if(this.connected){
					sucess = true;
					return; //se ja esta ligado sai
				}

				if(disconnect) //se desligou voluntariamente sai
					return;

				//estabelece a licacao (TCP)
				if(this.socket != null)
					this.socket.close(); //just to be sure

				/*
				 * Ao fim de x tentativas liga-se ao watchdog
				 */
				if(i>MAX_ATTEMPTS/2){
					client.com.write("A tentar o servidor de backup");
					this.client.host = WATCHDOG;
					this.client.listenerPort = TCP_LISTENER_PORT_ALTERNATIVE;
				}

				this.socket = new Socket(client.host, client.listenerPort);
				out = new ObjectOutputStream(this.socket.getOutputStream());
				in = new ObjectInputStream(this.socket.getInputStream());


				sucess = true;
				connected = true;
				this.setServerStatus(connected);

				if(!client.firstConnection)
					this.client.initiateConnection();

				this.repeatAll();
				return; //consegui ligar-me
			}catch (Exception e){

			}finally {

				if(client.firstConnection && !sucess){
					this.client.host = WATCHDOG;
					this.client.listenerPort = TCP_LISTENER_PORT_ALTERNATIVE;
				}

				if(disconnect){
					sucess = true;
					return; //disconnect foi intencional
				}
				else if (!sucess){
					this.setServerStatus(false);
					try{
						if(this.socket != null)
							this.socket.close(); //primeiro fecho o socket
					}catch (IOException io){
						io.printStackTrace();
					}
					connected = false;
					//espera alguns segundos ate voltar a tentar
					System.out.print("\nConnection Error! Reconnecting.");
					sleep(4000);
				}
				else{
					synchronized(mutex){
						mutex.notifyAll(); //avisa a malta que ja estamos ligados outra vez
					}
				}
			}//finally
		}//for
		//se nao consegui ligar ate aqui: desiste
		if(!disconnect)
			client.com.write("Nao foi possivel ligar ao servidor!\n\nA terminar...");
		terminate();

	}

	@Override
	public boolean realSend(Message m) {

//				if(!connected)
//					return false;
//		
//				try {
//					out.writeObject(m);
//					out.flush();
//				} catch (IOException e) {
//					System.out.println("Error writting to OutputStream");
//					return false;
//				}

		if(!connected)
			return false;

		try {
			this.queue.put(m);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return true;
	}

	@Override
	public void realTerminate() {
		//		synchronized (mutex){
		//			mutex.notifyAll();
		//			try {
		//				if(this.socket != null)
		//					this.socket.close();
		//			} catch (IOException e) {
		//				System.out.println("Error closing socket: "+e.getMessage());
		//			}
		//		}
		disconnect = true;
		connected = false;
	}

	private class NetworkIn extends Thread{

//		SocketConnection conn;
//		NetworkIn(SocketConnection sc){
//			this.conn = sc;
//		}

		public void run(){
			while(true){
				synchronized (mutex){
					while(!connected || disconnect){
						try {
							mutex.wait();
						} catch (InterruptedException e) {
							//e.printStackTrace();
							System.out.println("Erro na ligacao - InterruptedException");
						}
					}
				}

				while(true){

					try {
						receive( (Message) in.readObject());
						//						client.inbox.put(new Envelope((Message) in.readObject(),conn));
					} catch (IOException e) {
						connect();
					} catch (ClassNotFoundException e) {
						connect();
					}
				}
			}
		}
	}

	private class NetworkOut extends Thread{

		BlockingQueue<Message> outbox;
		public NetworkOut(BlockingQueue<Message> c){
			this.outbox = c;
		}

		public void run(){

				while(!disconnect){
					try {
						//TODO
						Message m = outbox.take();
						out.writeObject(m);
						out.flush();
					} catch (Exception e) {
//						System.out.println("Deu excepcao a escrever para o objectOutputStream - vou fechar o socket");
//						synchronized (mutex){
//							mutex.notifyAll();
//							try {
//								if(socket != null)
//									socket.close();
//							} catch (IOException ex) {
//								System.out.println("Error closing socket: "+ex.getMessage());
//							}
//						}
//					} catch (InterruptedException e) {
//						// TODO Auto-generated catch block
//						e.printStackTrace();
//					}
						connect();
					}
				}
//				synchronized (mutex){
//					mutex.notifyAll();
//					try {
//						if(socket != null)
//							socket.close();
//					} catch (IOException ex) {
//						System.out.println("Error closing socket: "+ex.getMessage());
//					}
//				}
			}
	}

	@Override
	public void noConnectionWarning() {
		client.com.write("WARNING: no connection > command discarded\n");
	}

	@Override
	public boolean realSend(Message m, SelectionKey key) {
		// TODO Auto-generated method stub
		return false;
	}
}

